---
title: Application business rules
summary: Every business rule reachable from NDReimbursementWebApp's execution paths, with per-rule trigger, enforcement, outcome, origin and entry point.
status: generated
owner: SAIF Case Management
last_reviewed: 2026-10-20
---

# Application business rules

This page is the complete catalog of business rules that govern NDReimbursementWebApp. A rule appears here when it is enforced on a code path the application executes, whether the enforcing code is the application's own page model, manager and start-up code, or behavior inherited from the BolMiddleware and SAIF.NetCore packages. The application has no local libraries and no stored procedures of its own, so there are no library or stored procedure rules in scope.

Every rule heading below is the bare rule ID, so a link such as `business-rules.md#br-scr-007` resolves to the rule.

## Rule scope and coverage

Reachability was traced from the application's entry points: the page handlers of the single [NDReimbursement](../screens/ndreimbursement.md) screen, the four [hosted endpoints](hosted-endpoints.md), and the `Startup` and page-model infrastructure. Behavior of the four API-client packages (NDReimbursementAPI.Client, DigitalPaymentAPIClient, DocumentsCoreAPI.Client, OneIncAPIClient) is not documented anywhere retrievable, so any rules they enforce are unknown**.

| Category | Section | Count |
| --- | --- | --- |
| Application-level (cross-cutting) | [Application-level business rules](#application-level-business-rules) | 8 |
| Screen-enforced | [Screen business rules](#screen-business-rules) | 11 |
| Library-defined, in scope | None: the application has no local libraries | 0 |
| Stored procedure-enforced | None: the application reaches no database directly | 0 |
| Inherited from framework packages | [Inherited business rules](#inherited-business-rules) | 12 |
| **Total in scope** | | **31** |
| Library-defined, not reachable | [Rules not reachable from this application](#rules-not-reachable-from-this-application) | 0 |

## Rule index

| Rule ID | Statement | Origin | Reached from |
| --- | --- | --- | --- |
| [BR-APP-001](#br-app-001) | Access to the page requires any one of the four NDR roles. | Application | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| [BR-APP-002](#br-app-002) | Without a current user id, `PopulateModel` returns false and sets `ErrorMessage` to "Invalid User"; display is subject to the view condition `!IsSuccessful && !HasPolicyNumber`. | Application | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| [BR-APP-003](#br-app-003) | Capability flags are derived per request, and a user with none of the four NDR roles is denied at the page-level gate (the `PopulateModel` "Not Authorized" fallback is a defensive branch). | Application | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| [BR-APP-004](#br-app-004) | External updaters may not change policy context. | Application | [Change policy context](operations/change-policy-context.md) |
| [BR-APP-005](#br-app-005) | The developer-access type overrides the role flags. | Application | [NDReimbursement](../screens/ndreimbursement.md) |
| [BR-APP-006](#br-app-006) | All POST handlers run without anti-forgery validation. | Application | [Save requested amounts](operations/save-requested-amounts.md), [NDReimbursement](../screens/ndreimbursement.md) |
| [BR-APP-007](#br-app-007) | The request-form value limit is 30000. | Application | [Save requested amounts](operations/save-requested-amounts.md), [Pay by mail](operations/pay-by-mail.md) |
| [BR-APP-008](#br-app-008) | The application is served under a path base and the empty route maps to the screen. | Application | [Load screen](operations/load-screen.md) |
| [BR-SCR-001](#br-scr-001) | The due date is effective at 18:00; after that claims are read-only. | [NDReimbursement](../screens/ndreimbursement.md) | [Load screen](operations/load-screen.md) |
| [BR-SCR-002](#br-scr-002) | A due-date override must be between today and the due date plus 90 days (UI limits only). | [NDReimbursement](../screens/ndreimbursement.md) | [Override due date](operations/override-due-date.md) |
| [BR-SCR-003](#br-scr-003) | A claim is editable only when the due date has not passed and its allowed amount is above zero. | [NDReimbursement](../screens/ndreimbursement.md) | [Load screen](operations/load-screen.md) |
| [BR-SCR-004](#br-scr-004) | Claims with bill code CF are flagged as cash flow. | [NDReimbursement](../screens/ndreimbursement.md) | [Load screen](operations/load-screen.md) |
| [BR-SCR-005](#br-scr-005) | Reimburse all sets each requested amount to the allowed amount. | [NDReimbursement](../screens/ndreimbursement.md) | [NDReimbursement](../screens/ndreimbursement.md) |
| [BR-SCR-006](#br-scr-006) | Reimbursable claims exist when claims are editable and Reimburse all applies or the user is a policyholder. | [NDReimbursement](../screens/ndreimbursement.md) | [Load screen](operations/load-screen.md) |
| [BR-SCR-007](#br-scr-007) | Pay online requires 0 < total <= max, with max supplied by the client. | [NDReimbursement](../screens/ndreimbursement.md) | [Pay online](operations/pay-online.md) |
| [BR-SCR-008](#br-scr-008) | Pending online payments block the "claims saved" state. | [NDReimbursement](../screens/ndreimbursement.md) | [Load screen](operations/load-screen.md) |
| [BR-SCR-009](#br-scr-009) | An HTTP 400 from the statements call yields the invalid-policy state. | [NDReimbursement](../screens/ndreimbursement.md) | [Load screen](operations/load-screen.md) |
| [BR-SCR-010](#br-scr-010) | Developer-access options depend on the policy number and the selected type. | [NDReimbursement](../screens/ndreimbursement.md) | [Load screen](operations/load-screen.md) |
| [BR-SCR-011](#br-scr-011) | The claims area renders only when a policy, an NDR statement and access are all present. | [NDReimbursement](../screens/ndreimbursement.md) | [Load screen](operations/load-screen.md) |
| [INH-001](#inh-001) | The BOL web context resolves in a fixed order. | `BolMiddleware`† | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md) |
| [INH-002](#inh-002) | Policy access is validated softly through `AccessAllowed`. | `BolMiddleware`† | [Load screen](operations/load-screen.md) |
| [INH-003](#inh-003) | Remote context, menu and header failures are replaced with defaults. | `BolMiddleware`† | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md) |
| [INH-004](#inh-004) | A user is external when the identity domain is not SAIF. | `BolMiddleware`† | [Load screen](operations/load-screen.md) |
| [INH-005](#inh-005) | External users' policy associations are filtered by user type. | `BolMiddleware`† | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md) |
| [INH-006](#inh-006) | A session policy header is reused only on a policy match. | `BolMiddleware`† | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md) |
| [INH-007](#inh-007) | Policy dropdown sorting, and duplicate policy numbers raise an exception. | `BolMiddleware`† | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md) |
| [INH-008](#inh-008) | The `BolContext` cookie lives ten seconds and lacks `Secure` and `SameSite`. | `BolMiddleware`† | [Change policy context](operations/change-policy-context.md) |
| [INH-009](#inh-009) | `SecurityApiAuthorize` roles are evaluated with OR semantics. | `SAIF.NetCore.AuthService`† | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| [INH-010](#inh-010) | The Windows domain is stripped before the Security API call. | `SAIF.NetCore.AuthService`† | [Load screen](operations/load-screen.md) |
| [INH-011](#inh-011) | A non-success Security API response throws. | `SAIF.NetCore.AuthService`† | [Load screen](operations/load-screen.md) |
| [INH-012](#inh-012) | Windows users always authorize remotely. | `SAIF.NetCore.AuthService`† | [Load screen](operations/load-screen.md) |

## Application-level business rules

Rules composed at the application level, enforced by `NDReimbursementsModel` or `Startup` with no single owning library.

## BR-APP-001

| Field | Value |
| --- | --- |
| **Title** | Page access requires an NDR role |
| **Statement** | Access to the page requires any one of the four NDR roles: `NonDisReimbViewResource`, `NonDisReimbUpdateResource`, `NonDisReimbViewExtResource`, `NonDisReimbUpdateExtResource`. |
| **Trigger** | Any request to the page |
| **Enforcement** | `[SecurityApiAuthorize]` with arguments on `NDReimbursementsModel`. The declaration is application code and the evaluation is package code†. |
| **Outcome** | The request is denied when the user holds none of the roles ([INH-009](#inh-009)) |
| **Origin** | Application (declaration); `SAIF.NetCore.AuthService`† (evaluation) |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-APP-002

| Field | Value |
| --- | --- |
| **Title** | Missing user is rejected |
| **Statement** | A request without a `CurrentUserId` is rejected: `PopulateModel` sets `ErrorMessage` to "Invalid User" and returns false. |
| **Trigger** | `PopulateModel` |
| **Enforcement** | Check in `NDReimbursementsModel.PopulateModel` |
| **Outcome** | No data is loaded. The guard returns before `PopulateModel` assigns `HasPolicyNumber`, so that property keeps the earlier value set in `OnRequestStart` from `Header`. The view's `ErrorMessage` block renders only when `!IsSuccessful && !HasPolicyNumber`, so whether "Invalid User" is displayed depends on that pre-existing value |
| **Origin** | Application |
| **Reached from** | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md), [Pay by mail](operations/pay-by-mail.md), [Override due date](operations/override-due-date.md), [Delete statement](operations/delete-statement.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-APP-003

| Field | Value |
| --- | --- |
| **Title** | Capability flags are derived per request |
| **Statement** | The page derives the flags `IsInternalUpdater`, `IsExternalUpdater`, `IsInternalITUser`, `CanPrint`, `IsInternalReadOnly` and `IsExternalReadOnly` on every request. A user with none of the four NDR roles is denied first by the page-level `[SecurityApiAuthorize]` gate, which admits only those four roles, so such a request is ordinarily rejected before `PopulateModel`. If the fallback branch in `PopulateModel` is reached, it sets `IsInvalidUser` and `ErrorMessage` to "Not Authorized" and returns false; that message is not guaranteed to be displayed (see Outcome). |
| **Trigger** | `PopulateModel` |
| **Enforcement** | `NDReimbursementsModel.PopulateModel` |
| **Outcome** | The view is shaped by the flags. The handlers do not enforce the flags (TD-APP-003). In the fallback branch the handler sets the error state only; the view's `ErrorMessage` block renders only when `!IsSuccessful && !HasPolicyNumber`, so display depends on the `HasPolicyNumber` value. |
| **Origin** | Application |
| **Reached from** | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md), [Pay by mail](operations/pay-by-mail.md), [Override due date](operations/override-due-date.md), [Delete statement](operations/delete-statement.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-APP-004

| Field | Value |
| --- | --- |
| **Title** | External updaters cannot change policy context |
| **Statement** | External updaters may not change the policy context: `ChangeContextPrivilege = !IsExternalUpdater`. |
| **Trigger** | Page model construction |
| **Enforcement** | `NDReimbursementsModel` constructor |
| **Outcome** | The context switch is hidden in the layout**. The server does not enforce the restriction on the reload-context handler. |
| **Origin** | Application |
| **Reached from** | [Change policy context](operations/change-policy-context.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-APP-005

| Field | Value |
| --- | --- |
| **Title** | Developer-access type overrides role flags |
| **Statement** | The developer-access type overrides the role flags: `PolicyHolder` forces `IsExternalUpdater` and `DueDate` forces `IsInternalUpdater`. |
| **Trigger** | The `DevAccess` handler |
| **Enforcement** | `NDReimbursementsModel` |
| **Outcome** | The UI flags escalate to updater capabilities. The handler has no IT or QA check (TD-APP-002). |
| **Origin** | Application |
| **Reached from** | [NDReimbursement](../screens/ndreimbursement.md) (direct POST only; the form is commented out in the view) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-APP-006

| Field | Value |
| --- | --- |
| **Title** | POST handlers skip anti-forgery validation |
| **Statement** | Every POST handler runs without anti-forgery validation. |
| **Trigger** | Every POST |
| **Enforcement** | `[IgnoreAntiforgeryToken]` on `NDReimbursementsModel`, in addition to the same attribute on `BolMenuModel`† |
| **Outcome** | The handlers are exposed to cross-site request forgery (TD-APP-001) |
| **Origin** | Application |
| **Reached from** | [Save requested amounts](operations/save-requested-amounts.md), [Pay by mail](operations/pay-by-mail.md), [Pay online](operations/pay-online.md), [Override due date](operations/override-due-date.md), [Delete statement](operations/delete-statement.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-APP-007

| Field | Value |
| --- | --- |
| **Title** | Form value limit of 30000 |
| **Statement** | The request-form size limit is 30000 values. |
| **Trigger** | A form post |
| **Enforcement** | `[RequestFormLimits(ValueCountLimit = 30000)]` on `NDReimbursementsModel` |
| **Outcome** | Large claim tables can be posted |
| **Origin** | Application |
| **Reached from** | [Save requested amounts](operations/save-requested-amounts.md), [Pay by mail](operations/pay-by-mail.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-APP-008

| Field | Value |
| --- | --- |
| **Title** | Path base and empty route |
| **Statement** | The application is served under the path base `/NDReimbursementWebApp`, and the empty route maps to the NDReimbursement page. |
| **Trigger** | Every request |
| **Enforcement** | `UsePathBase` and `AddPageRoute("/NDReimbursement", "")` in `Startup` |
| **Outcome** | URL shaping |
| **Origin** | Application |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Startup.cs |

---

## Screen business rules

Rules enforced directly in the NDReimbursement page, its manager or its services rather than delegated to a package.

## BR-SCR-001

| Field | Value |
| --- | --- |
| **Title** | Due date effective at 18:00 |
| **Statement** | The due date is effective at 18:00. After that time claims are read-only (`DueDatePassed`). |
| **Trigger** | Load and render |
| **Enforcement** | `NDReimbursementManager.GetDateData` and `NDReimbursementsModel` |
| **Outcome** | Claims render read-only |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Managers/NDR/NDReimbursementManager.cs; src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-SCR-002

| Field | Value |
| --- | --- |
| **Title** | Due-date override window |
| **Statement** | A due-date override must be between today and the due date plus 90 days. Only the UI limits enforce this. |
| **Trigger** | The due-date override form |
| **Enforcement** | `NDReimbursementsModel` computes the limits and `wwwroot/js/DatePicker.js` applies them |
| **Outcome** | The server does not re-check the window (TD-APP-008) |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [Override due date](operations/override-due-date.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs; src/NDReimbursementWebApp/wwwroot/js/DatePicker.js |

---

## BR-SCR-003

| Field | Value |
| --- | --- |
| **Title** | Claim editability |
| **Statement** | A claim is editable only when the due date has not passed and its allowed amount is greater than zero. |
| **Trigger** | Render |
| **Enforcement** | `NDReimbursementManager` |
| **Outcome** | The requested-amount input is editable |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Managers/NDR/NDReimbursementManager.cs |

---

## BR-SCR-004

| Field | Value |
| --- | --- |
| **Title** | Cash-flow claim flag |
| **Statement** | A claim whose `ReimbursementBillCode` is `CF` is flagged as cash flow. |
| **Trigger** | Render |
| **Enforcement** | `NDReimbursementManager` |
| **Outcome** | A warning indicator is shown |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Managers/NDR/NDReimbursementManager.cs |

---

## BR-SCR-005

| Field | Value |
| --- | --- |
| **Title** | Reimburse all |
| **Statement** | Reimburse all sets each requested amount to the allowed amount. |
| **Trigger** | The Reimburse all handler |
| **Enforcement** | `NDReimbursementsModel` and `NDReimbursementManager` |
| **Outcome** | The claims are updated and the screen is redisplayed |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs; src/NDReimbursementWebApp/Managers/NDR/NDReimbursementManager.cs |

---

## BR-SCR-006

| Field | Value |
| --- | --- |
| **Title** | Reimbursable claims exist |
| **Statement** | Reimbursable claims exist when claims are editable and either Reimburse all applies or the user is a policyholder. |
| **Trigger** | Render |
| **Enforcement** | `NDReimbursementsModel` |
| **Outcome** | The Reimburse all button renders only then |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-SCR-007

| Field | Value |
| --- | --- |
| **Title** | Pay-online amount check |
| **Statement** | Pay online requires `0 < total <= max`, where `max` is supplied by the client. |
| **Trigger** | The Pay online handler |
| **Enforcement** | `NDReimbursementsModel` |
| **Outcome** | When the check fails no payment request is created (the response is null). The client-supplied maximum is a weakness (TD-APP-006). |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [Pay online](operations/pay-online.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-SCR-008

| Field | Value |
| --- | --- |
| **Title** | Pending online payments |
| **Statement** | Pending online payments, meaning the NDR flag or a digital-payment count above zero, block the "claims saved" state. A 404 from the digital payment API counts as zero. |
| **Trigger** | Load |
| **Enforcement** | `NDReimbursementsModel` and `DigitalPaymentService` |
| **Outcome** | A tooltip is shown and the state changes |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs; src/NDReimbursementWebApp/Services/DigitalPaymentService.cs |

---

## BR-SCR-009

| Field | Value |
| --- | --- |
| **Title** | Invalid policy and no NDR statements |
| **Statement** | When `GetPolicyStatements` throws an HTTP status exception, the handler sets `HasInvalidPolicyNumber` and `ErrorMessage` ("is not a valid policy number" for HTTP 400) and returns false; it does not set `HasNDR` false. When there are no NDR statements, `HasNDR` is false and `NdrWarningMessage` is set. |
| **Trigger** | Load |
| **Enforcement** | `NDReimbursementsModel` |
| **Outcome** | Handler: flags and messages are set and loading stops. View: the `ErrorMessage` block renders only when `!IsSuccessful && !HasPolicyNumber` and `HasInvalidPolicyNumber` is not referenced, so the invalid-policy message is not rendered while a policy number is present; the warning message shows only when `ErrorMessage` is empty |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-SCR-010

| Field | Value |
| --- | --- |
| **Title** | Developer-access options |
| **Statement** | The user options (developer access types) depend on the policy number and the selected type. |
| **Trigger** | Load |
| **Enforcement** | `NDReimbursementManager` and `NDReimbursementsModel` |
| **Outcome** | The dropdown content changes. The dropdown form itself is commented out in the view. |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Managers/NDR/NDReimbursementManager.cs; src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs |

---

## BR-SCR-011

| Field | Value |
| --- | --- |
| **Title** | Claims area render condition |
| **Statement** | The claims area renders only when `HasPolicyNumber && HasNDR && AccessAllowed`. `AccessAllowed` comes from the base page model†. |
| **Trigger** | Render |
| **Enforcement** | Razor view `Pages/NDReimbursement.cshtml` |
| **Outcome** | The claims area is hidden otherwise |
| **Origin** | [NDReimbursement](../screens/ndreimbursement.md) |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Source** | src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml |

---

No model validation exists: no handler checks `ModelState` and no data annotations or FluentValidation are used (TD-APP-012).

## Inherited business rules

Rules that reach the application through base classes and attributes provided by BolMiddleware and SAIF.NetCore.AuthService. They are enforced by inherited behavior, not by code in this repository. The consuming type for every rule is `NDReimbursementsModel` (or `Startup`) in this application.

## INH-001

| Field | Value |
| --- | --- |
| **Title** | Web context resolution order |
| **Statement** | The BOL web context resolves in order: `BolContext` cookie, re-read cookie, referring application, `WebContextId` form value or `cid` query value, session, new context.† |
| **Trigger** | Every page request |
| **Enforcement** | `BolContextModel` lifecycle in the base page-model chain |
| **Outcome** | The context carries the policy number the screen loads |
| **Origin** | `BolMiddleware`† — `BolContextModel` |
| **Reached from** | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-002

| Field | Value |
| --- | --- |
| **Title** | Soft policy access check |
| **Statement** | Policy access is validated through `IsValidPolicyForCurrentUser`. A failure sets an error message and `AccessAllowed = false`. Enforcement is soft: the derived page must check `AccessAllowed`.† |
| **Trigger** | Policy header load |
| **Enforcement** | `BolPolicyModel`; the page checks `AccessAllowed` in `Pages/NDReimbursement.cshtml` ([BR-SCR-011](#br-scr-011)) |
| **Outcome** | The claims area is hidden. Handlers do not check the flag. |
| **Origin** | `BolMiddleware`† — `BolPolicyModel` |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-003

| Field | Value |
| --- | --- |
| **Title** | Remote service failures replaced with defaults |
| **Statement** | Failures in the remote context, menu and policy header services are logged and replaced with empty or default results.† |
| **Trigger** | A failing BOL context, menu or policy header call |
| **Enforcement** | Base page-model chain |
| **Outcome** | The page loads with empty chrome or defaults instead of failing |
| **Origin** | `BolMiddleware`† — `BolContextModel`, `BolMenuModel`, `BolPolicyModel` |
| **Reached from** | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-004

| Field | Value |
| --- | --- |
| **Title** | External user definition |
| **Statement** | A user is external when the domain of the authenticated identity name is not `SAIF`. External users get profile and logoff links.† |
| **Trigger** | Layout and context load |
| **Enforcement** | BolMiddleware identity handling |
| **Outcome** | Drives the external-user outcomes of role checks and menu links |
| **Origin** | `BolMiddleware`† — `BolPolicyModel` |
| **Reached from** | [Load screen](operations/load-screen.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-005

| Field | Value |
| --- | --- |
| **Title** | External policy association filtering |
| **Statement** | For external users, policy associations are filtered by user type: `POLCY` keeps those for the customer id, `AGNCY` keeps those whose policy `AgencyId` matches.† |
| **Trigger** | Policy header and dropdown load |
| **Enforcement** | BolMiddleware policy header service |
| **Outcome** | External users see only their own policies |
| **Origin** | `BolMiddleware`† — `BolPolicyModel` |
| **Reached from** | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-006

| Field | Value |
| --- | --- |
| **Title** | Session policy header reuse |
| **Statement** | A session policy header is reused only when it matches the requested policy number. Otherwise it is fetched and saved to session.† |
| **Trigger** | Policy header load |
| **Enforcement** | BolMiddleware policy header service |
| **Outcome** | Avoids a stale header for a different policy |
| **Origin** | `BolMiddleware`† — `BolPolicyModel` |
| **Reached from** | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-007

| Field | Value |
| --- | --- |
| **Title** | Policy dropdown ordering and duplicates |
| **Statement** | Policy dropdown entries sort by policy number descending with the current selection first. Duplicate policy numbers raise `ArgumentException`.† |
| **Trigger** | Policy dropdown build |
| **Enforcement** | BolMiddleware menu and header components |
| **Outcome** | Ordered dropdown, or an exception for duplicates |
| **Origin** | `BolMiddleware`† — `BolMenuModel` |
| **Reached from** | [Load screen](operations/load-screen.md), [Change policy context](operations/change-policy-context.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-008

| Field | Value |
| --- | --- |
| **Title** | BolContext cookie attributes |
| **Statement** | The `BolContext` cookie lives ten seconds and is `HttpOnly`, without `Secure` or `SameSite`.† |
| **Trigger** | Saving the web context |
| **Enforcement** | `SaveContext` in the base page-model chain |
| **Outcome** | The cookie is sent over any scheme and in cross-site requests |
| **Origin** | `BolMiddleware`† — `BolContextModel` |
| **Reached from** | [Change policy context](operations/change-policy-context.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-009

| Field | Value |
| --- | --- |
| **Title** | SecurityApiAuthorize OR semantics |
| **Statement** | `[SecurityApiAuthorize("A,B")]` sets policy `SecurityAPI.A,B`. Roles are evaluated with OR semantics through the remote Security API.† |
| **Trigger** | Every request to the page ([BR-APP-001](#br-app-001)) |
| **Enforcement** | `SecurityApiAuthorize` and its policy provider |
| **Outcome** | A user holding any one listed role is admitted |
| **Origin** | `SAIF.NetCore.AuthService`† — `SecurityApiAuthorize` |
| **Reached from** | [Load screen](operations/load-screen.md), [NDReimbursement](../screens/ndreimbursement.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-010

| Field | Value |
| --- | --- |
| **Title** | Domain stripped before the Security API call |
| **Statement** | The handler strips the Windows domain before calling the Security API. `SecurityClient` rewrites backslashes in user ids to forward slashes.† |
| **Trigger** | Role evaluation |
| **Enforcement** | `SecurityApiHandler` and `SecurityClient` |
| **Outcome** | The Security API receives a domain-less user id |
| **Origin** | `SAIF.NetCore.AuthService`† — `SecurityApiHandler` |
| **Reached from** | [Load screen](operations/load-screen.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-011

| Field | Value |
| --- | --- |
| **Title** | Security API failure throws |
| **Statement** | A non-successful Security API response throws `SecurityClientException` rather than denying normally.† |
| **Trigger** | Role evaluation when the Security API fails |
| **Enforcement** | `SecurityClient` |
| **Outcome** | The request fails with an exception instead of an access-denied result |
| **Origin** | `SAIF.NetCore.AuthService`† — `SecurityClient` |
| **Reached from** | [Load screen](operations/load-screen.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## INH-012

| Field | Value |
| --- | --- |
| **Title** | Windows users authorize remotely |
| **Statement** | Token users authorize locally through a role claim first, then through the Security API. Windows users always authorize remotely.† |
| **Trigger** | Role evaluation |
| **Enforcement** | `SecurityApiHandler` |
| **Outcome** | Windows-authenticated users incur a Security API call on each evaluation |
| **Origin** | `SAIF.NetCore.AuthService`† — `SecurityApiHandler` |
| **Reached from** | [Load screen](operations/load-screen.md) |
| **Consuming type** | `NDReimbursementsModel` in this application |

---

## Rules not reachable from this application

None. The application has no local libraries, so no library-documented rule is excluded. [INH-001](#inh-001) to [INH-012](#inh-012) are reachable through the page-model base chain, `Startup` or the `SecurityApiAuthorize` attribute. The packages DigitalPaymentAPIClient, DocumentsCoreAPI.Client, NDReimbursementAPI.Client and OneIncAPIClient have no retrievable rules, so what they enforce is unknown**.

> **Note:** Statements marked with `†` are derived from dependency library
> documentation via the Legends CLI. Statements marked with `**` could
> not be confirmed; treat as unknown until verified.

## Related documentation
- [Application index](index.md)
- [Overview](overview.md)
- [Architecture](architecture.md)
- [Dependencies](dependencies.md)
- [Configuration](configuration.md)
- [Operations and processes](operations-processes.md)
- [Security and authorization](security-authorization.md)
- [Data model](data-model.md)
- [Hosted endpoints](hosted-endpoints.md)
- [Tech debt](tech-debt.md)

## Sources
- src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml.cs
- src/NDReimbursementWebApp/Pages/NDReimbursement.cshtml
- src/NDReimbursementWebApp/Managers/NDR/NDReimbursementManager.cs
- src/NDReimbursementWebApp/Services/DigitalPaymentService.cs
- src/NDReimbursementWebApp/Startup.cs
- src/NDReimbursementWebApp/wwwroot/js/DatePicker.js
